using Gj;
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;

// This system updates all entities in the scene with both a RotationSpeed_ForEach and Rotation component.

// ReSharper disable once InconsistentNaming
[WorldSystemFilter(WorldSystemFilterFlags.Disabled)]
public partial class CastSkillSystem : SystemBase
{
    private EntityQuery cast_hit_Group;
    private EntityQuery cast_target_Group;
    private EntityQuery cast_targets_Group;

    private BeginSimulationEntityCommandBufferSystem CommandBufferSystem;
    private NativeParallelMultiHashMap<Entity, OnSkillsData> onSkillsDataHashMap;
    private EntityQuery ray_hit_Group;

    protected override void OnCreate()
    {
        CommandBufferSystem = World.GetOrCreateSystemManaged<BeginSimulationEntityCommandBufferSystem>();
        onSkillsDataHashMap = new NativeParallelMultiHashMap<Entity, OnSkillsData>(100, Allocator.Persistent);
        cast_target_Group = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<SkillData>(),
                ComponentType.ReadOnly<CastTargetData>()
            }
        });

        cast_targets_Group = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<SkillData>(),
                ComponentType.ReadOnly<CastTargetsData>()
            }
        });

        cast_hit_Group = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<SkillData>(),
                ComponentType.ReadOnly<CastHitsData>()
            }
        });

        ray_hit_Group = GetEntityQuery(new EntityQueryDesc
        {
            All = new[]
            {
                ComponentType.ReadOnly<SkillData>(),
                ComponentType.ReadOnly<RayHitsData>()
            }
        });
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
        onSkillsDataHashMap.Dispose();
    }

    // OnUpdate runs on the main thread.
    protected override void OnUpdate()
    {
        onSkillsDataHashMap.Clear();
        var inputDeps = Dependency;
        var time = SystemAPI.Time.ElapsedTime;
        var commandBuffer = CommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
        var EntityType = GetEntityTypeHandle();
        var entitiesData = GetComponentLookup<EntityData>();
        var lifeEntitiesData = GetComponentLookup<LifeEntityData>();
        var castTargetDataType = GetComponentTypeHandle<CastTargetData>();
        var skillDataType = GetComponentTypeHandle<SkillData>();
        GetBufferTypeHandle<OnSkillsData>();

        inputDeps = new CastTargetJob
        {
            CommandBuffer = commandBuffer,
            EntityType = EntityType,
            CastTargetDataType = castTargetDataType,
            SkillDataType = skillDataType,
            entitiesData = entitiesData,
            lifeEntitiesData = lifeEntitiesData,
            onSkillsDataHashMap = onSkillsDataHashMap.AsParallelWriter()
        }.Schedule(cast_target_Group, inputDeps);

        var castTargetsDataType = GetBufferTypeHandle<CastTargetsData>();
        inputDeps = new CastTargetsJob
        {
            CommandBuffer = commandBuffer,
            EntityType = EntityType,
            CastTargetsDataType = castTargetsDataType,
            SkillDataType = skillDataType,
            entitiesData = entitiesData,
            lifeEntitiesData = lifeEntitiesData,
            onSkillsDataHashMap = onSkillsDataHashMap.AsParallelWriter()
        }.Schedule(cast_targets_Group, inputDeps);

        var castHitsDataType = GetBufferTypeHandle<CastHitsData>();
        inputDeps = new CastHitsJob
        {
            CommandBuffer = commandBuffer,
            EntityType = EntityType,
            CastHitsDataType = castHitsDataType,
            SkillDataType = skillDataType,
            entitiesData = entitiesData,
            lifeEntitiesData = lifeEntitiesData,
            onSkillsDataHashMap = onSkillsDataHashMap.AsParallelWriter()
        }.Schedule(cast_hit_Group, inputDeps);

        var rayHitsDataType = GetBufferTypeHandle<RayHitsData>();
        inputDeps = new RayHitsJob
        {
            CommandBuffer = commandBuffer,
            EntityType = EntityType,
            RayHitsDataType = rayHitsDataType,
            SkillDataType = skillDataType,
            entitiesData = entitiesData,
            lifeEntitiesData = lifeEntitiesData,
            onSkillsDataHashMap = onSkillsDataHashMap.AsParallelWriter()
        }.Schedule(ray_hit_Group, inputDeps);

        //        inputDependencies.Complete();
        //        var arrays = onSkillsDataHashMap.GetKeyArray(Allocator.Temp);
        //        var e = arrays.GetEnumerator();
        //        while (e.MoveNext())
        //        {
        //            var key = e.Current;
        //            var v = onSkillsDataHashMap.GetValuesForKey(e.Current);
        //            while (v.MoveNext())
        //            {
        //                var value = v.Current;
        //                var entityData = entitiesData[key];
        //                var life = ObjectService.GetObject<Life>(entityData.id);
        //                if (life != null)
        //                {
        //                    var targetData = entitiesData[value.entity];
        //                    var target = ObjectService.GetObject<Cast>(targetData.id);
        //                    if (target != null) {
        //                        life.OnSkill(target, EngineService.single.GetSkill(value.skillId), value.power, value.time);
        //                    }
        //                }
        //            }
        //        }
        //        e.Dispose();
        inputDeps = new BindSkillJob
        {
            onSkillsDataHashMap = onSkillsDataHashMap,
            entitiesData = entitiesData
        }.Schedule(inputDeps);

        CommandBufferSystem.AddJobHandleForProducer(inputDeps);
        inputDeps.Complete();
    }

    [BurstCompile]
    private struct CastTargetJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        //        [ReadOnly] public BufferFromEntity<OnSkillsData> onSkillsData;
        [WriteOnly] public NativeParallelMultiHashMap<Entity, OnSkillsData>.ParallelWriter onSkillsDataHashMap;
        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public ComponentTypeHandle<SkillData> SkillDataType;
        [ReadOnly] public ComponentTypeHandle<CastTargetData> CastTargetDataType;
        [ReadOnly] public ComponentLookup<EntityData> entitiesData;
        [ReadOnly] public ComponentLookup<LifeEntityData> lifeEntitiesData;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var entities = chunk.GetNativeArray(EntityType);
            var skillDataArray = chunk.GetNativeArray(SkillDataType);
            var castTargetDataArray = chunk.GetNativeArray(CastTargetDataType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var entity = entities[i];
                CommandBuffer.RemoveComponent<CastTargetData>(unfilteredChunkIndex, entity);
                var skillData = skillDataArray[i];
                var castTargetData = castTargetDataArray[i];
                if (!entitiesData.HasComponent(skillData.master)) continue;
                var master = entitiesData[skillData.master];

                if (!entitiesData.HasComponent(castTargetData.entity)) continue;
                var target = entitiesData[castTargetData.entity];
                if (!lifeEntitiesData.HasComponent(castTargetData.entity)) continue;
                if (!lifeEntitiesData[castTargetData.entity].isLive) continue;
                if (UnityTools.AllowCast(skillData, master, target))
                    //                    var buffer = onSkillsData[castTargetData.entity];
                    //                    buffer.Add(new OnSkillsData {entity = skillData.master, skillId = skillData.skillId});
                    onSkillsDataHashMap.Add(castTargetData.entity,
                        new OnSkillsData
                        {
                            entity = skillData.master, skillId = skillData.skillId, power = skillData.power,
                            time = skillData.time
                        });
            }
        }
    }

    [BurstCompile]
    private struct CastTargetsJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        //        [ReadOnly] public BufferFromEntity<OnSkillsData> onSkillsData;
        [WriteOnly] public NativeParallelMultiHashMap<Entity, OnSkillsData>.ParallelWriter onSkillsDataHashMap;
        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public BufferTypeHandle<CastTargetsData> CastTargetsDataType;
        [ReadOnly] public ComponentTypeHandle<SkillData> SkillDataType;
        [ReadOnly] public ComponentLookup<EntityData> entitiesData;
        [ReadOnly] public ComponentLookup<LifeEntityData> lifeEntitiesData;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var entities = chunk.GetNativeArray(EntityType);
            var castTargetsDataArray = chunk.GetBufferAccessor(CastTargetsDataType);
            var skillDataArray = chunk.GetNativeArray(SkillDataType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var entity = entities[i];
                CommandBuffer.RemoveComponent<CastTargetsData>(unfilteredChunkIndex, entity);
                var skillData = skillDataArray[i];
                if (!entitiesData.HasComponent(skillData.master)) continue;
                var master = entitiesData[skillData.master];
                var castTargetsDatas = castTargetsDataArray[i];
                for (var ii = 0; ii < castTargetsDatas.Length; ii++)
                {
                    var castTargetsData = castTargetsDatas[ii];
                    if (!entitiesData.HasComponent(castTargetsData.entity)) continue;
                    var target = entitiesData[castTargetsData.entity];
                    if (!lifeEntitiesData.HasComponent(castTargetsData.entity)) continue;
                    if (!lifeEntitiesData[castTargetsData.entity].isLive) continue;
                    if (UnityTools.AllowCast(skillData, master, target))
                        //                        var buffer = onSkillsData[castHitsData.entity];
                        //                        buffer.Add(new OnSkillsData {entity = skillData.master, skillId = skillData.skillId});
                        onSkillsDataHashMap.Add(castTargetsData.entity,
                            new OnSkillsData
                            {
                                entity = skillData.master, skillId = skillData.skillId, power = skillData.power,
                                time = skillData.time
                            });
                }
            }
        }
    }

    [BurstCompile]
    private struct CastHitsJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [WriteOnly] public NativeParallelMultiHashMap<Entity, OnSkillsData>.ParallelWriter onSkillsDataHashMap;
        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public BufferTypeHandle<CastHitsData> CastHitsDataType;
        [ReadOnly] public ComponentTypeHandle<SkillData> SkillDataType;
        [ReadOnly] public ComponentLookup<EntityData> entitiesData;
        [ReadOnly] public ComponentLookup<LifeEntityData> lifeEntitiesData;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var entities = chunk.GetNativeArray(EntityType);
            var castHitsDataArray = chunk.GetBufferAccessor(CastHitsDataType);
            var skillDataArray = chunk.GetNativeArray(SkillDataType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var entity = entities[i];
                CommandBuffer.RemoveComponent<CastHitsData>(unfilteredChunkIndex, entity);
                var skillData = skillDataArray[i];
                if (!entitiesData.HasComponent(skillData.master)) continue;
                var master = entitiesData[skillData.master];
                var castHitsDatas = castHitsDataArray[i];
                for (var ii = 0; ii < castHitsDatas.Length; ii++)
                {
                    var castHitsData = castHitsDatas[ii];
                    if (!entitiesData.HasComponent(castHitsData.entity)) continue;
                    var target = entitiesData[castHitsData.entity];
                    if (!lifeEntitiesData.HasComponent(castHitsData.entity)) continue;
                    if (!lifeEntitiesData[castHitsData.entity].isLive) continue;
                    if (UnityTools.AllowCast(skillData, master, target))
                        onSkillsDataHashMap.Add(castHitsData.entity,
                            new OnSkillsData
                            {
                                entity = skillData.master, skillId = skillData.skillId, power = skillData.power,
                                time = skillData.time
                            });
                }
            }
        }
    }

    [BurstCompile]
    private struct RayHitsJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [WriteOnly] public NativeParallelMultiHashMap<Entity, OnSkillsData>.ParallelWriter onSkillsDataHashMap;
        [ReadOnly] public EntityTypeHandle EntityType;
        [ReadOnly] public BufferTypeHandle<RayHitsData> RayHitsDataType;
        [ReadOnly] public ComponentTypeHandle<SkillData> SkillDataType;
        [ReadOnly] public ComponentLookup<EntityData> entitiesData;
        [ReadOnly] public ComponentLookup<LifeEntityData> lifeEntitiesData;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var entities = chunk.GetNativeArray(EntityType);
            var rayHitsDataArray = chunk.GetBufferAccessor(RayHitsDataType);
            var skillDataArray = chunk.GetNativeArray(SkillDataType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var entity = entities[i];
                CommandBuffer.RemoveComponent<RayHitsData>(unfilteredChunkIndex, entity);
                var skillData = skillDataArray[i];
                if (!entitiesData.HasComponent(skillData.master)) continue;
                var master = entitiesData[skillData.master];
                var rayHitsDatas = rayHitsDataArray[i];
                for (var ii = 0; ii < rayHitsDatas.Length; ii++)
                {
                    var rayHitsData = rayHitsDatas[ii];
                    if (!entitiesData.HasComponent(rayHitsData.entity)) continue;
                    var target = entitiesData[rayHitsData.entity];
                    if (!lifeEntitiesData.HasComponent(rayHitsData.entity)) continue;
                    if (!lifeEntitiesData[rayHitsData.entity].isLive) continue;
                    if (UnityTools.AllowCast(skillData, master, target))
                        onSkillsDataHashMap.Add(rayHitsData.entity,
                            new OnSkillsData
                            {
                                entity = skillData.master, skillId = skillData.skillId, power = skillData.power,
                                time = skillData.time
                            });
                }
            }
        }
    }

    private struct BindSkillJob : IJob
    {
        [ReadOnly] public NativeParallelMultiHashMap<Entity, OnSkillsData> onSkillsDataHashMap;
        [ReadOnly] public ComponentLookup<EntityData> entitiesData;

        public void Execute()
        {
            var entitys = onSkillsDataHashMap.GetKeyArray(Allocator.Temp);
            for (var i = 0; i < entitys.Length; i++)
            {
                var entity = entitys[i];

                var entityData = entitiesData[entity];
                var life = ObjectService.GetObject<Life>(entityData.id);
                if (life != null)
                {
                    var skills = onSkillsDataHashMap.GetValuesForKey(entity);
                    foreach (var value in skills)
                    {
                        var targetData = entitiesData[value.entity];
                        var target = ObjectService.GetObject<DataObject>(targetData.id);
                        if (target != null)
                            life.OnSkill(target, EngineService.single.GetSkill(value.skillId), value.power, value.time);
                    }

                    skills.Dispose();
                }
            }
        }
    }
}